pacenotes_installer\wizard\scene/
game_folder.rs1use windows_sys::Win32::Foundation::{HWND, LPARAM, WPARAM};
2use windows_sys::Win32::UI::Controls as ctl;
3use windows_sys::Win32::UI::WindowsAndMessaging as wm;
4
5const DETECT_DEBOUNCE_MS: u32 = 400;
8const TIMER_DETECT: usize = 1;
9
10const EN_CHANGE: u16 = 0x0300;
11const BN_CLICKED: u16 = 0;
12const CBN_SELCHANGE: u16 = 1;
13
14pub unsafe extern "system" fn game_folder_page(
16 dialog: HWND,
17 message: u32,
18 wparam: WPARAM,
19 lparam: LPARAM,
20) -> isize {
21 match message {
22 wm::WM_INITDIALOG => {
23 crate::wizard::page::adopt_state(dialog, lparam);
24 crate::ui::set_text(
25 dialog,
26 crate::wizard::control_ids::IDC_S3_PATH_LABEL,
27 crate::i18n::IDS_S3_PATH,
28 );
29 crate::ui::set_text(
30 dialog,
31 crate::wizard::control_ids::IDC_S3_GAME_LABEL,
32 crate::i18n::IDS_S3_GAME,
33 );
34 crate::ui::set_text(
35 dialog,
36 crate::wizard::control_ids::IDC_S3_BROWSE,
37 crate::i18n::IDS_BTN_BROWSE,
38 );
39 for (control, text) in [
40 (
41 crate::wizard::control_ids::IDC_S3_ROUTES,
42 crate::i18n::IDS_S3_ROUTES,
43 ),
44 (
45 crate::wizard::control_ids::IDC_S3_DOWNLOAD,
46 crate::i18n::IDS_S3_DOWNLOAD,
47 ),
48 (
49 crate::wizard::control_ids::IDC_S3_CONFIGURE,
50 crate::i18n::IDS_S3_CONFIGURE,
51 ),
52 (
53 crate::wizard::control_ids::IDC_S3_RESTORE,
54 crate::i18n::IDS_S3_RESTORE,
55 ),
56 ] {
57 crate::ui::set_text(dialog, control, text);
58 }
59 super::game_folder_routes::enter_takes_main_route(dialog);
60 show_carried_folder(dialog);
61 1
62 }
63 wm::WM_COMMAND => {
64 on_command(dialog, wparam);
65 1
66 }
67 wm::WM_TIMER if wparam == TIMER_DETECT => {
68 wm::KillTimer(dialog, TIMER_DETECT);
69 redetect(dialog, false);
70 1
71 }
72 wm::WM_NOTIFY => match crate::wizard::page::notify_code(lparam) {
73 Some(ctl::PSN_SETACTIVE) => {
74 crate::wizard::page::set_sheet_caption(dialog);
75 crate::wizard::page::set_header(dialog, crate::i18n::IDS_S3_TITLE);
76 crate::wizard::page::set_buttons(dialog, ctl::PSWIZB_BACK);
78 crate::wizard::page::set_next_text(dialog, crate::i18n::IDS_BTN_NEXT);
79 super::game_folder_routes::show_route_buttons(dialog);
80 crate::wizard::page::reply(dialog, 0)
81 }
82 Some(ctl::PSN_QUERYCANCEL) => crate::wizard::page::query_cancel(dialog),
83 Some(ctl::NM_CUSTOMDRAW) => super::game_folder_routes::paint_main_route(dialog, lparam),
84 _ => 0,
85 },
86 _ => 0,
87 }
88}
89
90unsafe fn on_command(dialog: HWND, wparam: WPARAM) {
91 let control = crate::ui::loword(wparam);
92 let code = crate::ui::hiword(wparam);
93 match (control, code) {
94 (crate::wizard::control_ids::IDC_S3_PATH, EN_CHANGE) => {
95 wm::SetTimer(dialog, TIMER_DETECT, DETECT_DEBOUNCE_MS, None);
96 }
97 (crate::wizard::control_ids::IDC_S3_BROWSE, BN_CLICKED) => on_browse(dialog),
98 (crate::wizard::control_ids::IDC_S3_GAME, CBN_SELCHANGE) => on_game_picked(dialog),
99 (crate::wizard::control_ids::IDC_S3_DOWNLOAD, BN_CLICKED) => {
100 super::game_folder_routes::take_route(dialog, crate::wizard::state::Route::Download)
101 }
102 (crate::wizard::control_ids::IDC_S3_CONFIGURE, BN_CLICKED) => {
103 super::game_folder_routes::take_route(dialog, crate::wizard::state::Route::Configure)
104 }
105 (crate::wizard::control_ids::IDC_S3_RESTORE, BN_CLICKED) => {
106 super::game_folder_routes::take_route(dialog, crate::wizard::state::Route::Restore)
107 }
108 _ => {}
109 }
110}
111
112unsafe fn on_browse(dialog: HWND) {
114 let prompt = crate::i18n::load_string(crate::i18n::IDS_S3_PATH);
115 let Some(picked) = crate::ui::browse_for_folder(dialog, &prompt) else {
116 return;
117 };
118 crate::ui::set_text_raw(dialog, crate::wizard::control_ids::IDC_S3_PATH, &picked);
119 redetect(dialog, true);
120}
121
122unsafe fn show_carried_folder(dialog: HWND) {
125 let carried = crate::wizard::page::state(dialog)
126 .map(|state| state.folder.clone())
127 .filter(|folder| !folder.is_empty());
128 match carried {
129 Some(folder) => {
130 crate::ui::set_text_raw(dialog, crate::wizard::control_ids::IDC_S3_PATH, &folder);
131 redetect(dialog, false);
132 }
133 None => show_detection(dialog),
134 }
135}
136
137unsafe fn redetect(dialog: HWND, complain_if_none: bool) {
139 let folder = crate::ui::get_text(dialog, crate::wizard::control_ids::IDC_S3_PATH);
140 let detected = pacenotes_installer::gamedir::detect(&folder);
141 let Some(state) = crate::wizard::page::state(dialog) else {
142 return;
143 };
144 state.folder = folder;
145 state.game = match state.game {
146 Some(previous) if detected.contains(&previous) => Some(previous),
147 _ => detected.first().copied(),
148 };
149 state.detected = detected;
150 let none_found = state.detected.is_empty();
151
152 show_detection(dialog);
153 super::game_folder_routes::show_route_buttons(dialog);
154 if complain_if_none && none_found {
155 warn_no_game(dialog);
156 }
157}
158
159unsafe fn show_detection(dialog: HWND) {
162 let combo = crate::wizard::control_ids::IDC_S3_GAME;
163 let (detected, chosen) = match crate::wizard::page::state(dialog) {
164 Some(state) => (state.detected.clone(), state.game),
165 None => (Vec::new(), None),
166 };
167
168 crate::ui::combo_reset(dialog, combo);
169 if detected.is_empty() {
170 crate::ui::combo_add(
171 dialog,
172 combo,
173 &crate::i18n::load_string(crate::i18n::IDS_S3_DETECTING),
174 );
175 crate::ui::combo_select(dialog, combo, 0);
176 crate::ui::enable(dialog, combo, false);
177 return;
178 }
179 for game in &detected {
180 crate::ui::combo_add(dialog, combo, game.title());
181 }
182 let selected = chosen
183 .and_then(|chosen| detected.iter().position(|game| *game == chosen))
184 .unwrap_or(0);
185 crate::ui::combo_select(dialog, combo, selected);
186 crate::ui::enable(dialog, combo, detected.len() > 1);
187}
188
189unsafe fn on_game_picked(dialog: HWND) {
190 let selection = crate::ui::combo_selection(dialog, crate::wizard::control_ids::IDC_S3_GAME);
191 if let (Some(index), Some(state)) = (selection, crate::wizard::page::state(dialog)) {
192 state.game = state.detected.get(index).copied();
193 }
194 super::game_folder_routes::show_route_buttons(dialog);
195}
196
197pub(super) unsafe fn warn_no_game(dialog: HWND) {
198 crate::ui::message_box_warning(
199 dialog,
200 &crate::i18n::load_string(crate::i18n::IDS_S3_NOGAME),
201 );
202}